#include <string.h>
#include "sht3xx.h"

extern int32_t aos_debug_printf(const char *fmt, ...);
#define PKG_USING_SHT3X
#ifdef PKG_USING_SHT3X
#define CRC_POLY 0x31 

static aos_mutex_t mutex = NULL;

/**
 * This function write 2-byte cmd to SHT3x 
 *
 * @param dev the pointer of device driver structure
 * @param cmd the 16bit command to be sent to SHT3x
 *
 * @return the cmd transfer status, 0 reprensents setting successfully.
 */
static signed long write_cmd(sht3x_device_t dev, unsigned short cmd)
{
    unsigned char buf[2] ;
    buf[0] = cmd >> 8 ;
    buf[1] = cmd & 0xFF ;

    if(rvm_hal_iic_master_send(dev->i2c,  dev->sht3x_addr, (uint8_t *)buf, 2, I2C_TX_TIMEOUT) == 0)
        return 0;
    else
        return -1;
}

/**
 *  This function read len bytes from dev and store the bytes in to buf 
 * 
 * @param dev the pointer of device driver structure
 * @param buf the pointer to buffer array 
 * @param len the number of bytes to be read 
 * 
 * @return the i2c read status, 0 represents success.
 */
static signed long read_bytes(sht3x_device_t dev, unsigned char * buf, unsigned char len)
{
    if(rvm_hal_iic_master_recv(dev->i2c, dev->sht3x_addr, (uint8_t *)buf, len, I2C_RX_TIMEOUT) == 0)
    {
        return 0 ;
    }else
    {
        return - 1 ;
    }
    
}

/**
 * This function calculate CRC value of bytes in buffer
 * CRC_POLY is predefined as 0x31
 * 
 * @param buf the pointer to buffer array 
 * @param len the length of buffer array 
 * 
 * @return calculated CRC value.
 */
static unsigned char crc8(unsigned char * buf, unsigned char len)
{
    unsigned char crc = 0xFF ;
    unsigned char i, j ;
    for(j = len; j; j --){
        crc ^= *buf ++ ;
        for( i = 8; i; i --){
            crc = (crc & 0x80) ? (crc << 1) ^ CRC_POLY : (crc << 1) ;
        }
    }
    return crc ;
}

/** 
 * This function read temperature and humidity by single shot mode 
 * Attention: 
 *  - aos_msleep() is called to wait for SHT3x to be ready to read
 *  - the temperature and humidity is stored in the device driver structure
 * 
 * @param dev the pointer of device driver structure
 * 
 * @return the status of read data from SHT3x, 0 means success.
 */
signed long sht3x_read_singleshot(sht3x_device_t dev)
{
    unsigned char temp[6] ;
    signed long result;
    CHECK_RET_WITH_RET(dev, -1);

    result = aos_mutex_lock(dev->lock, AOS_NO_WAIT);

    if (result == 0)
    {
        if( write_cmd(dev, dev->cmd_readout) == 0)
        {
			// wait about 5 ms
			aos_msleep(20);
            if(read_bytes(dev, temp, 6) == 0){
                if(crc8(temp, 2) == temp[2]){
                    dev->temperature = -45.0 + (temp[1] | temp[0] << 8) * 175.0 / (0xFFFF - 1) ; //sensor temperature convert to reality
                    result = 0 ;
                }else{
                    result = -1 ;
                }
                if(crc8(temp + 3, 2) == temp[5]){
                    dev->humidity = (temp[4] | temp[3] << 8) * 0.0015259022 ; //sensor humidity convert to reality
                }else{
                    result = -1 ;
                }
            }else
            {
                result = -1 ;
            }
        }
        else
        {
            result = -1 ;
        }
	aos_mutex_unlock(dev->lock);
    }
    else
    {
        aos_debug_printf("Taking mutex of SHT3x failed.");
        result = -1;
    }
	
    return result;
}

/**
 * This function resets all parameter with default
 *
 * @param dev the pointer of device driver structure
 *
 * @return the softreset status, 0 reprensents setting successfully.
 */
signed long sht3x_softreset(sht3x_device_t dev)
{
    CHECK_RET_WITH_RET(dev, -1);
    
    if( write_cmd(dev, CMD_SOFT_RESET) == 0)
    {
        return 0;
    }
    else
    {
        return 1 ;
    }
}

/** 
 * This function clear the status register in SHT3x  
 * 
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, 0 means success.
 */
signed long sht3x_clear_status(sht3x_device_t dev)
{
    CHECK_RET_WITH_RET(dev, -1);

    if( write_cmd(dev, CMD_CLEAR_STATUS) == 0)
    {
        return 0 ;
    }
    else
    {
        return 1 ;
    }
}

/**
 * This function read the status register from SHT3x
 * Attention:
 *  - the status word is stored in device driver structure
 * 
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, 0 means success.
 */
signed long sht3x_read_status(sht3x_device_t dev)
{
    unsigned char buf[3];
    CHECK_RET_WITH_RET(dev, -1);
    if( write_cmd(dev, CMD_READ_STATUS) == 0)
    {
        read_bytes(dev, buf, 3);
        if( crc8(buf, 2) == buf[2])
        {
            dev->status.status_word = ( buf[0] << 8 ) | buf[1];
            return 0 ;
        }
    }

    return -1 ;
}

/**
 * This function enable heater
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, 0 means success.
 */
signed long sht3x_enable_heater(sht3x_device_t dev)
{
    CHECK_RET_WITH_RET(dev, -1);
    if( write_cmd(dev, CMD_HEATER_ENABLE) == 0)
    {
        return 0 ;
    }
    else
    {
        return -1 ;
    }
}

/**
 * This function disable heater
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, 0 means success.
 */
signed long sht3x_disable_heater(sht3x_device_t dev)
{
    CHECK_RET_WITH_RET(dev, -1);
    if( write_cmd(dev, CMD_HEATER_DISABLE) == 0)
    {
        return 0 ;
    }else
    {
        return -1 ;
    }
}

/**
 * This function write accelerated response time command to SHT3x
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, 0 means success.
 */
signed long sht3x_acc_resp_time(sht3x_device_t dev)
{
    CHECK_RET_WITH_RET(dev, -1);
    if( write_cmd(dev, CMD_ART) == 0)
    {
        return 0 ;
    }
    else
    {
        return -1 ;
    }
}

/**
 * This function write break command to SHT3x to break out of continuous readout mode
 * @param dev the pointer of device driver structure
 * 
 * @return the command transfer status, 0 means success.
 */
signed long sht3x_break(sht3x_device_t dev)
{
    CHECK_RET_WITH_RET(dev, -1);
    if( write_cmd(dev, CMD_BREAK) == 0)
    {
        return 0 ;
    }else
    {
        return -1 ;
    }
}

/**
 * This function initializes sht3x registered device driver
 *
 * @param i2c_bus_name the name of i2c device used by SHT3x device
 * @param sht3x_addr the address of SHT3x device
 * 
 * @return the pointer to sht3x device driver structure.
 */
sht3x_device_t sht3x_init(const char *bus_name,  unsigned int iic_idx, unsigned char sht3x_addr)
{
    int result;
    sht3x_device_t dev;
    char filename[8] = {0};
    rvm_hal_iic_config_t config = {0};

    dev = calloc(1, sizeof(struct sht3x_device));

    if (dev == NULL)
    {
        aos_debug_printf("Can't allocate memory for sht3x device on '%s' ", bus_name);
        return NULL;
    }

    if(sht3x_addr == SHT3X_ADDR_PD || sht3x_addr == SHT3X_ADDR_PU){
        dev->sht3x_addr = sht3x_addr ;
    }else{
        aos_debug_printf("Illegal sht3x address:'%x'", sht3x_addr);
        free(dev);
        return NULL;
    }

    sprintf(filename, "iic%d", iic_idx);

    /* Register Device */
    rvm_iic_drv_register(iic_idx);

    /* Get device handle */
    dev->i2c = rvm_hal_iic_open(filename);

    /* Initialize as default parameter */
    rvm_hal_iic_config_default(&config);

    /* Configure device parameters */
    rvm_hal_iic_config(dev->i2c, &config);

    if (dev->i2c == NULL)
    {
        aos_debug_printf("Can't find sht3x device on '%s' ", bus_name);
        free(dev);
        return NULL;
    }

    result = aos_mutex_new(&mutex);
    dev->lock = &mutex;

    if (dev->lock == NULL)
    {
        aos_debug_printf("Can't create mutex for sht3x device on '%s' ", bus_name);
        free(dev);
        return NULL;
    }

    /* I2C streching disabled, medium repeatability for default single shot readout */
    dev->cmd_readout = CMD_MEAS_POLLING_L ;

	 /* clear the status register */
	sht3x_clear_status(dev);
	
    return dev;
}

/**
 * This function releases memory and deletes mutex lock
 *
 * @param dev the pointer of device driver structure
 */
int sht3x_deinit(sht3x_device_t dev)
{
    CHECK_RET_WITH_RET(dev, 1);

    aos_mutex_free(dev->lock);
    free(dev);
    return 0;
}

/**
 * This function is exported to MSH commands list 
 * Usage example:
 *  - sht3x probe i2c1 pu : initialize sht3x device on i2c1 bus with address pin pulled up(i2c address 0x45)
 *  - sht3x probe i2c1: initialize sht3x device one i2c1 bus with address pin pulled down by default(i2c address 0x44)
 *  - sht3x read: read and print temperature and humidity from previously initialized sht3x
 */
void sht3x(int argc, char *argv[])
{
    int id = 0; 
    char* usr_str;
    static sht3x_device_t dev = NULL;
    unsigned char sht_addr = SHT3X_ADDR_PD ;
    
    if (argc > 1)
    {
        if (!strcmp(argv[1], "probe"))
        {
            if (argc >= 3)
            {
                /* initialize the sensor when first probe */
          
                    /* deinit the old device */
                    if(dev)
                    {
						aos_debug_printf("Deinit sht3x");
                        sht3x_deinit(dev);
                    }
                    // no else needed here
                    if( argc > 3)
                    {
                        if( !strcmp("pd", argv[3]))
                        {
                            sht_addr = SHT3X_ADDR_PD ;
                        }
                        else if( !strcmp("pu", argv[3]))
                        {
                            sht_addr = SHT3X_ADDR_PU ;
                        }
                        else
                        {
                            aos_debug_printf("Illegal sht3x address, using 0x44 by default\n");
                            sht_addr = SHT3X_ADDR_PD ; // pulled down by default: 0x44 
                        }
                    }
                    // no else needed here

                    int len = strlen(argv[2]);

                    if( len > 3)
                    {
                        strtok(argv[2],"c");
                        usr_str = strtok(NULL,"c");
                        id = atoi(usr_str);
                    }

                    dev = sht3x_init(argv[2], id, sht_addr);
                    if(!dev)
                    {
                        aos_debug_printf("sht3x probe failed, check input args\n");
                    }else
					{
						aos_debug_printf("sht3x probed, addr:0x%x\n", sht_addr) ;
					}
                
            }
            else
            {
                aos_debug_printf("sht3x probe <i2c dev name>   - probe sensor by given name\n");
            }
        }
        else if (!strcmp(argv[1], "read"))
        {
            if (dev)
            {
                /* read the sensor data */
                sht3x_read_singleshot(dev);

                aos_debug_printf("sht3x humidity   : %d.%d \n", (int)dev->humidity, (int)(dev->humidity * 10) % 10);
                aos_debug_printf("sht3x temperature: %d.%d \n", (int)dev->temperature, (int)(dev->temperature * 10) % 10);
            }
            else
            {
                aos_debug_printf("Please using 'sht3x probe <i2c dev name> <pu/pd>' first\n");
            }
        }
		else if (!strcmp(argv[1], "status"))
		{
			if(dev)
			{
				if(sht3x_read_status(dev) == 0)
				{
					aos_debug_printf("sht3x status:\n");
					aos_debug_printf("\tchecksum:\t%d\t- 0 means checksum correct\n", dev->status.bits.checksum_ok);
					aos_debug_printf("\tcommand:\t%d\t- 0 means last cmd executed OK\n", dev->status.bits.command_ok);
					aos_debug_printf("\treset deteced:\t%d\n", dev->status.bits.reset_detected);
					aos_debug_printf("\talert pending:\t%d\n", dev->status.bits.alert_pending);
					aos_debug_printf("\tT track alert:\t%d\n", dev->status.bits.T_tracking_alert);
					aos_debug_printf("\tRH track alert:\t%d\n", dev->status.bits.RH_tracking_alert);
					aos_debug_printf("\theater enabled:\t%d\n", dev->status.bits.heater);
				}else
				{
					aos_debug_printf("sht3x status not read\n");
				}
			}else
			{
				aos_debug_printf("Please using 'sht3x probe <i2c dev name> <pu/pd>' first\n");
			}
		}
		else if (!strcmp(argv[1], "reset"))
		{
			if(dev)
			{
				if(sht3x_softreset(dev) == 0)
				{
					aos_debug_printf("sht3x reset cmd sent\n");
				}else
				{
					aos_debug_printf("sht3x reset cmd not sent\n");
				}
			}else
			{
				aos_debug_printf("Please using 'sht3x probe <i2c dev name> <pu/pd>' first\n");
			}
		}
		else if (!strcmp(argv[1], "heater"))
		{
            if(argc > 2)
            {
                if(dev)
                {
                    if( !strcmp(argv[2], "on"))
                    {
                        if(sht3x_enable_heater(dev) == 0)
                        {
                            aos_debug_printf("sht3x heater cmd sent\n");
                        }else
                        {
                            aos_debug_printf("sht3x heater cmd not sent\n");
                        }
                    }else if( !strcmp(argv[2], "off"))
                    {
                        if(sht3x_disable_heater(dev) == 0)
                        {
                            aos_debug_printf("sht3x heater cmd sent\n");
                        }else
                        {
                            aos_debug_printf("sht3x heater cmd not sent\n");
                        }
                    }
                    else{
                        aos_debug_printf("Please input correct format:sht3x heater on/off\n");
                    }
                }else
                {
                    aos_debug_printf("Please using 'sht3x probe <i2c dev name> <pu/pd>' first\n");
                }
            }
            else
            {
                aos_debug_printf("Unknown command. Enter 'sht3x' for help\n");
            }
        }
    }
    else
    {
        aos_debug_printf("Usage:\n");
        aos_debug_printf("\tsht3x probe <i2c dev name> <pu/pd> -- probe sensor by i2c dev name and pull config\n");
        aos_debug_printf("\tsht3x read -- read sensor sht3x data\n");
		aos_debug_printf("\tsht3x status -- status register of sht3x\n");
		aos_debug_printf("\tsht3x reset -- send soft reset command to sht3x\n");
		aos_debug_printf("\tsht3x heater <on/off> -- turn on/off heater of sht3x\n");
    }
}

ALIOS_CLI_CMD_REGISTER(sht3x, sht3x, sht3x sensor);

#endif /* PKG_USING_SHT3X */
